package com.wumz.spring.write.v1.mvcframework.servlet;

import com.wumz.spring.write.v1.mvcframework.annotation.*;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

/**
 * Created by wumz on 2020/9/26 13:50
 */
public class MyDispatcherServlet extends HttpServlet {

    private Properties contextConfig = new Properties();

    //缓存所有扫描发现的类
    private List<String> CLASS_NAMES = new ArrayList<String>();

    //缓存所有实例化的bean  key: 默认类名首字母小写  value: 初始化的实例
    private Map<String, Object> BEAN_MAP = new HashMap<String, Object>();

    private Map<String, Method> HANDLER_MAPPING = new HashMap<String, Method>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //6、委派，根据URL去找到一个对应的Method并通过response返回
        try {
            doDispatch(req,resp);
        } catch (Exception e) {
            resp.getWriter().write("500 Exception " + Arrays.toString(e.getStackTrace()));
        }
    }

    /**
     * 根据URL去找到一个对应的Method并通过response返回
     * @param req
     * @param resp
     */
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException, InvocationTargetException, IllegalAccessException {

        Map<String, String[]> params = req.getParameterMap();
        String requestURI = req.getRequestURI();
        String contextPath = req.getContextPath();
        requestURI = requestURI.replace(contextPath, "").replaceAll("/+", "/");
        if(!HANDLER_MAPPING.containsKey(requestURI)){resp.getWriter().write("404 Not Found!!");return;}
        Method method = (Method) HANDLER_MAPPING.get(requestURI);
        //获取参数
        Class<?>[] parameterTypes = method.getParameterTypes();
        Object[] paramValues = new Object[parameterTypes.length];
        for (int i = 0; i < parameterTypes.length; i++) {
            Class<?> parameterType = parameterTypes[i];
            if (parameterType == HttpServletRequest.class){
                paramValues[i] = req;
            }
            if (parameterType == HttpServletResponse.class){
                paramValues[i] = resp;
            }
            if (parameterType == String.class){
                Annotation[][] parameterAnnotations = method.getParameterAnnotations();
                for (int j = 0; j < parameterAnnotations.length; j++) {
                    for (Annotation annotation : parameterAnnotations[j]) {
                        if (annotation instanceof MyRequestParam){
                            String value = ((MyRequestParam) annotation).value();
                            if (!"".equals(value.trim())){
                                String s = Arrays.toString(params.get(value))
                                        .replaceAll("\\[|\\]", "")
                                        .replaceAll("\\s+", "");
                                paramValues[i] = s;
                            }
                        }
                    }
                }
            }
        }



        method.invoke(BEAN_MAP.get(toLowerFirstCase(method.getDeclaringClass().getSimpleName())), new Object[]{req,resp,params.get("name")[0]});
    }

    //init方法肯定干得的初始化的工作
    //inti首先我得初始化所有的相关的类，IOC容器、servletBean
    @Override
    public void init(ServletConfig config) {

        try {
            //1、加载配置文件
            doLoadConfig(config);

            //2、扫描相关的类  取出配置文件中scanPackage的值
            doScanner(contextConfig.getProperty("scanPackage"));

            //3、初始化ioc容器，创建实例化并保存至容器
            doInitInstance();

            //4、DI赋值，扫描ioc容器中的实例，给没有赋值的属性自动赋值
            doAutoWrited();

            //5、做mvc，初始化HandlerMapping
            doHandlerMapping();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 初始化HandlerMapping
     */
    private void doHandlerMapping() {
        if (BEAN_MAP.isEmpty()){ return; }

        for (Map.Entry<String, Object> entry : BEAN_MAP.entrySet()) {

            Class<?> clazz = entry.getValue().getClass();
            if (!clazz.isAnnotationPresent(MyController.class)){ continue;}

            //相当于提取类上面的requestMapping的值
            String baseUrl = "";
            if (clazz.isAnnotationPresent(MyRequestMapping.class)){
                MyRequestMapping annotation = clazz.getAnnotation(MyRequestMapping.class);
                baseUrl = annotation.value();
            }
            //只获取public的方法
            for (Method method : clazz.getMethods()) {
                if (!method.isAnnotationPresent(MyRequestMapping.class)){ continue; }
                //相当于提取方法上面的requestMapping的值
                MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
                String url = ("/" + baseUrl + "/" + annotation.value()).replaceAll("/+", "/");
                HANDLER_MAPPING.put(url, method);
                System.out.println("Mapping" +url + "，" + method);
            }
        }
    }

    /**
     * DI赋值
     */
    private void doAutoWrited() throws IllegalAccessException {
        //如果CLASS_NAMES缓存中没有扫描到的类，直接跳过
        if (BEAN_MAP.isEmpty()){ return; }

        for (Map.Entry<String, Object> entry : BEAN_MAP.entrySet()) {
            //把所有的类型修饰的都取出来
            for (Field field : entry.getValue().getClass().getDeclaredFields()) {
                if (!field.isAnnotationPresent(MyAutowired.class)){ continue; }
                MyAutowired annotation = field.getAnnotation(MyAutowired.class);
                //如果用户没有自定义的beanName，就默认根据类型注入
                String beanName = annotation.value().trim();
                if ("".equals(beanName)){
                    //field.getType().getName() 获取字段的类型
                    beanName = field.getType().getName();
                }
                //暴力访问
                field.setAccessible(true);
                // BEAN_MAP.get(beanName) 通过接口的全名拿到接口的实现类的实例，再把这实例赋给这个字段
                field.set(entry.getValue(), BEAN_MAP.get(beanName));
            }
        }
    }

    /**
     * 初始化ioc容器
     */
    private void doInitInstance() {
        //如果CLASS_NAMES缓存中没有扫描到的类，直接跳过
        if (CLASS_NAMES.isEmpty()){ return; }

        try {
            for (String className : CLASS_NAMES) {
                Class<?> clazz = Class.forName(className);
                //创建实例是有条件的，至少你这个类有特定的注解,没有加注解，控制权不交给spring管理，自己管自己
                if (clazz.isAnnotationPresent(MyController.class)){
                    String beanName = toLowerFirstCase(clazz.getSimpleName());
                    //默认的类名首字母小写
                    BEAN_MAP.put(beanName, clazz.newInstance());
                }else if (clazz.isAnnotationPresent(MyService.class)){
                    //在多个包下出现相同的类名，只能自己起一个全局唯一名字，就比如 @Service("xxxxx")
                    String beanName = clazz.getAnnotation(MyService.class).value();
                    if ("".equals(beanName.trim())){
                        beanName = toLowerFirstCase(clazz.getSimpleName());
                    }
                    BEAN_MAP.put(beanName, clazz.newInstance());
                    //如果是接口，判断有多少个实现类，如果只有一个，默认选择这个实现类，如果有多个，只能抛异常
                    for (Class<?> i : clazz.getInterfaces()) {
                        if (BEAN_MAP.containsKey(i.getName())){
                            throw new Exception("The " + i.getName() + "IS exites!");
                        }
                        BEAN_MAP.put(i.getName(), clazz.newInstance());
                    }
                }else { continue; }
            }
        }catch (Exception e){ e.printStackTrace(); }
    }

    /**
     * 扫描相关的类
     * @param scanPackage
     */
    private void doScanner(String scanPackage) {
        //相当于把文件夹中的内容遍历出来，传过来的包名.类名 -> 包名/类名
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
        //转为file，就是classPath
        File classPath = new File(url.getFile());
        //当成一个文件夹，然后扫描这个文件夹下面所有的类
        for (File file : classPath.listFiles()) {

            //如果是文件夹类型，递归处理
            if (file.isDirectory()){
                doScanner(scanPackage + "." + file.getName());
            }else {
                //如果不是.class结尾的，我不管，直接跳过
                if (!file.getName().endsWith(".class")){ continue; }
                //不同包下面可以有相同的类名，这就有问题了，进行处理 用包名+类名进行缓存
                String className = (scanPackage + "." + file.getName().replace(".class", ""));
                CLASS_NAMES.add(className);
            }
        }
    }

    /**
     * 首字母转小写
     * @param replace
     * @return
     */
    private String toLowerFirstCase(String replace) {
        char[] chars = replace.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

    /**
     * 加载配置文件
     * @param config
     */
    private void doLoadConfig(ServletConfig config) {
        InputStream is = null;
        try {
            //加载web.xml中 <init-param> 的 <param-name> ，可自定义
            is = this.getClass().getClassLoader().getResourceAsStream(config.getInitParameter("contextConfigLocation"));
            //从字节输入流中读取键值对
            contextConfig.load(is);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (null != is){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
